package com.ce.socket;

import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.ByteArrayOutputStream;
import java.io.DataInputStream;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.net.Socket;
import java.net.UnknownHostException;
import java.nio.channels.SocketChannel;

import org.apache.log4j.Logger;


public class DalSocket {
	private static Logger log = Logger.getLogger(DalSocket.class);
	private String host;
	private Socket sock;
	private DalSocketPool pool;
	private int timeout;
	private int connectTimeout;
	private boolean noDelay;
	private DataInputStream in;
	private BufferedOutputStream out;
	
	private static String EMPTY_MESSAGE = "CM20NULL00GB00000020";
	private static byte[] BYTE_EMPTY_MESSAGE = EMPTY_MESSAGE.getBytes();
	
	public DalSocket( DalSocketPool pool, String host, int port, int timeout, int connectTimeout, boolean noDelay ) throws IOException, UnknownHostException {

		this.pool = pool;
		this.timeout = timeout;
		this.connectTimeout = connectTimeout;
		this.noDelay = noDelay;
		// get a socket channel
		sock = getSocket( host, port, connectTimeout );
		
		if ( timeout >= 0 )
			sock.setSoTimeout( timeout );

		// testing only
		sock.setTcpNoDelay( noDelay );

		// wrap streams
		in  = new DataInputStream( new BufferedInputStream( sock.getInputStream() ) );
		out = new BufferedOutputStream( sock.getOutputStream() );

		this.host = host + ":" + port;
		log.info( "=> Construction socket param: " + toParam() );
	}


	public DalSocket( DalSocketPool pool, String host, int timeout, int connectTimeout, boolean noDelay ) throws IOException, UnknownHostException {

		this.pool = pool;
		this.timeout = timeout;
		this.connectTimeout = connectTimeout;
		this.noDelay = noDelay;
		String[] ip = host.split(":");

		// get socket: default is to use non-blocking connect
		sock = getSocket( ip[ 0 ], Integer.parseInt( ip[ 1 ] ), connectTimeout );

		if ( timeout >= 0 )
			this.sock.setSoTimeout( timeout );

		// testing only
		sock.setTcpNoDelay( noDelay );

		// wrap streams
		in   = new DataInputStream( new BufferedInputStream( sock.getInputStream() ) );
		out  = new BufferedOutputStream( sock.getOutputStream() );

		this.host = host;
		log.info( "=> Construction socket param: " + toParam() );
	}
	
	private Socket getSocket( String host, int port, int timeout ) throws IOException {
		SocketChannel sock = SocketChannel.open();
		sock.socket().connect( new InetSocketAddress( host, port ), timeout );
		return sock.socket();
	}
	
//	private SocketChannel getChannel() { return sock.getChannel(); }

	public String getHost() { return this.host; }

	protected void trueClose() throws IOException {
		trueClose( true );
	}

	/** 
	 * closes socket and all streams connected to it 
	 *
	 * @throws IOException if fails to close streams or socket
	 */
	protected void trueClose( boolean addToDeadPool ) throws IOException {
		log.info( "=> Closing socket for real: " + toString() );

		boolean err = false;
		StringBuilder errMsg = new StringBuilder();

		if ( in != null ) {
			try {
				in.close();
			}
			catch( IOException ioe ) {
				log.error( "=> error closing input stream for socket: " + toString() + " for host: " + getHost() );
				log.error( ioe.getMessage(), ioe );
				errMsg.append( "=> error closing input stream for socket: " + toString() + " for host: " + getHost() + "\n" );
				errMsg.append( ioe.getMessage() );
				err = true;
			}
		}

		if ( out != null ) {
			try {
				out.close();
			}
			catch ( IOException ioe ) {
				log.error( "=> error closing output stream for socket: " + toString() + " for host: " + getHost() );
				log.error( ioe.getMessage(), ioe );
				errMsg.append( "=> error closing output stream for socket: " + toString() + " for host: " + getHost() + "\n" );
				errMsg.append( ioe.getMessage() );
				err = true;
			}
		}

		if ( sock != null ) {
			try {
				sock.close();
			}
			catch ( IOException ioe ) {
				log.error( "=> error closing socket: " + toString() + " for host: " + getHost() );
				log.error( ioe.getMessage(), ioe );
				errMsg.append( "=> error closing socket: " + toString() + " for host: " + getHost() + "\n" );
				errMsg.append( ioe.getMessage() );
				err = true;
			}
		}

		// check in to pool
		// whether add to deadpool, true add, false not add
		if ( addToDeadPool && sock != null )
			pool.recoverySocket( this, false );

		in = null;
		out = null;
		sock = null;

		if ( err )
			throw new IOException( errMsg.toString() );
	}


	/** 
	 * checks if the connection is open 
	 * 
	 * @return true if connected
	 */
	public boolean isConnected() {
		return ( sock != null && sock.isConnected() );
	}

	/*
	 * checks to see that the connection is still working
	 *
	 * @return true if still alive
	 */
	public boolean isAlive() {

		if ( !isConnected() )
			return false;

		// try to talk to the server w/ a dumb query to ask its version
		try {
			this.write( BYTE_EMPTY_MESSAGE );
			this.flush();
			this.read();
		}
		catch ( IOException ex ) {
			log.error( "=> isAlive error" , ex );
			return false;
		}

		return true;
	}

	
	public byte[] read() throws IOException {
		byte[] readbuf = new byte[1024];
		ByteArrayOutputStream out =  new ByteArrayOutputStream();
		int readSeek = 0;
		while((readSeek = in.read(readbuf)) != -1){
			out.write(readbuf, 0, readSeek);
			if(readSeek < 1024)
				break;
		}
		return out.toByteArray();
	}

	public void flush() throws IOException {
		if ( sock == null || !sock.isConnected() ) {
			log.error( "=> attempting to write to closed socket" );
			throw new IOException( "=> attempting to write to closed socket" );
		}
		//当主机关闭连接时，发送数据就会出错
		out.flush();
	}
	
	public void write( byte[] b ) throws IOException {
		if ( sock == null || !sock.isConnected() ) {
			log.error( "=> attempting to write to closed socket" );
			throw new IOException( "=> attempting to write to closed socket" );
		}
		out.write( b );
	}

	public String toString() {
		return ( sock == null ) ? "null" : sock.toString();
	}
	private String toParam() {
		return "[DalSocketPool=" + (pool==null?"null":pool.getPoolName())
				+ ", host=" + (host==null?"null":host)
				+ ", timeout=" + timeout
				+ ", connectTimeout=" + connectTimeout
				+ ", noDelay=" + noDelay;
	}
	
	/** 
	 * Hack to reap any leaking children. 
	 */
	protected void finalize() throws Throwable {
		try {
			if ( sock != null ) {
				log.error( "=> closing potentially leaked socket in finalize" );
				sock.close();
				sock = null;
			}
		}
		catch ( Throwable t ) {
			log.error( t.getMessage(), t );
		}
		finally {
			super.finalize();
		}
	}

}
